package com.dimples.dd.gateway.route;

import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.shaded.com.google.common.collect.Lists;
import com.dimples.dd.gateway.route.config.GatewayRoutersConfig;
import com.dimples.dd.gateway.route.enums.RouterDataType;
import com.dimples.dd.gateway.route.loader.model.MyRouteDefinition;
import com.dimples.dd.gateway.route.loader.model.Predicates;
import com.dimples.dd.gateway.route.loader.repository.DynamicRouteService;
import com.dimples.dd.redis.core.RedisHelper;
import com.dimples.dd.redis.core.constant.RedisConstants;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
import java.util.concurrent.Executor;

/**
 * 动态路由加载器
 *
 * @author zhongyj <1126834403@qq.com><br/>
 * @date 2024/9/13
 */
@Slf4j
@Component
@RefreshScope
@DependsOn({"gatewayRoutersConfig"})
public class DynamicRouteLoader implements ApplicationEventPublisherAware {

    /**
     * 默认超时时间
     */
    public static final long DEFAULT_TIMEOUT = 30000;
    /**
     * 需要拼接key的路由条件
     */
    private static final String[] GEN_KEY_ROUTERS = new String[]{"Path", "Host", "Method", "After", "Before", "Between", "RemoteAddr"};

    @Resource
    private GatewayRoutersConfig gatewayRoutersConfig;

    private ConfigService configService;

    private final DynamicRouteService dynamicRouteService;
    private ApplicationEventPublisher publisher;
    private final RedisHelper redisHelper;

    public DynamicRouteLoader(DynamicRouteService dynamicRouteService, ApplicationEventPublisher publisher, RedisHelper redisHelper) {
        this.dynamicRouteService = dynamicRouteService;
        this.publisher = publisher;
        this.redisHelper = redisHelper;
    }

    /**
     * 刷新路由
     */
    public void refresh(Map<String, Object> baseMap) {
        log.info("刷新路由, 路由模式 [ dataType：{} ]", gatewayRoutersConfig.getDataType());
        if (!RouterDataType.yml.toString().endsWith(gatewayRoutersConfig.getDataType())) {
            this.init(baseMap);
        }
    }

    /**
     * 初始化路由
     *
     * @param baseMap 数据库路由配置
     */
    public void init(Map<String, Object> baseMap) {
        log.info("初始化路由模式, [ dataType：{} ]", gatewayRoutersConfig.getDataType());
        if (RouterDataType.nacos.toString().endsWith(gatewayRoutersConfig.getDataType())) {
            loadRoutesByNacos();
        }
        //从数据库加载路由
        if (RouterDataType.database.toString().endsWith(gatewayRoutersConfig.getDataType())) {
            loadRoutesByRedis(baseMap);
        }
    }

    /**
     * 从nacos中读取路由配置
     */
    private void loadRoutesByNacos() {
        List<RouteDefinition> routes = Lists.newArrayList();
        this.configService = createConfigService();
        if (configService == null) {
            log.warn("initConfigService fail");
        }
        String dataId = gatewayRoutersConfig.getDataId();
        try {
            log.info("dd.route.config.data-id = {}", dataId);
            if (!StrUtil.endWithIgnoreCase(dataId, ".json")) {
                // 默认为json格式
                dataId = StrUtil.split(dataId, '.').get(0) + ".json";
            }
            log.info("nacos.config.group = {}", gatewayRoutersConfig.getRouteGroup());
            String configInfo = configService.getConfig(dataId, gatewayRoutersConfig.getRouteGroup(), DEFAULT_TIMEOUT);
            if (StrUtil.isNotBlank(configInfo)) {
                log.info("Nacos获取网关当前配置:\r\n{}", configInfo);
                routes = JSONUtil.toList(configInfo, RouteDefinition.class);
            } else {
                log.warn("ERROR: 从Nacos获取网关配置为空，请确认Nacos配置是否正确！");
            }
        } catch (NacosException e) {
            log.error("初始化网关路由时发生错误：{}", ExceptionUtil.stacktraceToString(e));
            return;
        }
        for (RouteDefinition definition : routes) {
            log.info("update route : {}", definition.toString());
            this.dynamicRouteService.add(definition);
        }
        this.publisher.publishEvent(new RefreshRoutesEvent(this));
        this.dynamicRouteByNacosListener(dataId, gatewayRoutersConfig.getRouteGroup());
    }

    /**
     * 创建ConfigService
     *
     * @return com.alibaba.nacos.api.config.ConfigService
     */
    private ConfigService createConfigService() {
        try {
            Properties properties = new Properties();
            properties.setProperty("serverAddr", gatewayRoutersConfig.getServerAddr());
            if (StrUtil.isNotBlank(gatewayRoutersConfig.getNamespace())) {
                properties.setProperty("namespace", gatewayRoutersConfig.getNamespace());
            }
            if (StrUtil.isNotBlank(gatewayRoutersConfig.getUsername())) {
                properties.setProperty("username", gatewayRoutersConfig.getUsername());
            }
            if (StrUtil.isNotBlank(gatewayRoutersConfig.getPassword())) {
                properties.setProperty("password", gatewayRoutersConfig.getPassword());
            }
            return configService = NacosFactory.createConfigService(properties);
        } catch (Exception e) {
            log.error("创建ConfigService异常", e);
            return null;
        }
    }

    /**
     * 监听Nacos下发的动态路由配置
     *
     * @param dataId 数据ID
     * @param group  分组
     */
    public void dynamicRouteByNacosListener(String dataId, String group) {
        try {
            configService.addListener(dataId, group, new Listener() {
                @Override
                public void receiveConfigInfo(String configInfo) {
                    log.info("进行网关更新:\n\r{}", configInfo);
                    List<MyRouteDefinition> definitionList = JSONUtil.toList(configInfo, MyRouteDefinition.class);
                    for (MyRouteDefinition definition : definitionList) {
                        log.info("update route : {}", definition.toString());
                        dynamicRouteService.update(definition);
                    }
                }

                @Override
                public Executor getExecutor() {
                    log.info("getExecutor\n\r");
                    return null;
                }
            });
        } catch (Exception e) {
            log.error("从nacos接收动态路由配置出错!!!", e);
        }
    }

    /**
     * 从redis中读取路由配置
     */
    private void loadRoutesByRedis(Map<String, Object> baseMap) {
        List<MyRouteDefinition> routes = Lists.newArrayList();
        configService = createConfigService();
        if (configService == null) {
            log.warn("initConfigService fail");
        }
        Object configInfo = this.redisHelper.get(RedisConstants.GATEWAY_ROUTES);
        if (ObjectUtil.isNotEmpty(configInfo)) {
            log.info("Redis获取网关当前配置:\r\n{}", JSONUtil.toJsonPrettyStr(configInfo));
            JSONArray array = JSONUtil.parseArray(configInfo.toString());
            try {
                routes = getRoutesByJson(array);
            } catch (URISyntaxException e) {
                log.error("网关信息处理失败: {}", ExceptionUtil.stacktraceToString(e));
                return;
            }
        } else {
            log.warn("ERROR: 从Redis获取网关配置为空，请确认system服务是否启动成功！");
        }

        for (MyRouteDefinition definition : routes) {
            log.info("update route : {}", definition.toString());
            Integer status = definition.getStatus();
            if (status.equals(0)) {
                dynamicRouteService.delete(definition.getId());
            } else {
                dynamicRouteService.add(definition);
            }
        }
        if (ObjectUtil.isNotEmpty(baseMap)) {
            String delRouterId = StrUtil.toStringOrNull(baseMap.get(RedisConstants.ROUTE_MESSAGE_DELETE_ROUTER_ID));
            if (ObjectUtil.isNotEmpty(delRouterId)) {
                dynamicRouteService.delete(delRouterId);
            }
        }
        this.publisher.publishEvent(new RefreshRoutesEvent(this));
    }

    /**
     * redis中的信息需要处理下 转成RouteDefinition对象
     * <p>
     * - id: login
     * <p>
     * uri: lb://dd-system
     * <p>
     * predicates:
     * <p>
     * - Path=/dd-system/sys/**,
     *
     * @param array jsonArray
     * @return List<MyRouteDefinition>
     */
    public static List<MyRouteDefinition> getRoutesByJson(JSONArray array) throws URISyntaxException {
        List<MyRouteDefinition> ls = new ArrayList<>();
        for (int i = 0; i < array.size(); i++) {
            JSONObject obj = array.getJSONObject(i);
            MyRouteDefinition route = new MyRouteDefinition();
            route.setId(obj.getStr("routerId"));
            route.setStatus(obj.getInt("status"));
            Object uri = obj.get("uri");
            if (uri == null) {
                route.setUri(new URI("lb://" + obj.getStr("name")));
            } else {
                route.setUri(new URI(obj.getStr("uri")));
            }
            Object predicates = obj.get("predicates");
            if (predicates != null) {
                List<Predicates> list = JSONUtil.toList(predicates.toString(), Predicates.class);
                //获取合并后的Predicates，防止配置多个path导致路径失效的问题
                Map<String, List<String>> groupedPredicates = new HashMap<>();
                for (Predicates predicatesVo : list) {
                    String name = predicatesVo.getName();
                    List<String> args = predicatesVo.getArgs();
                    groupedPredicates.computeIfAbsent(name, k -> new ArrayList<>()).addAll(args);
                }
                //合并后的list
                list = new ArrayList<>();
                for (Map.Entry<String, List<String>> entry : groupedPredicates.entrySet()) {
                    String name = entry.getKey();
                    List<String> args = entry.getValue();
                    list.add(new Predicates(name, args));
                }
                List<PredicateDefinition> predicateDefinitionList = new ArrayList<>();
                for (Object map : list) {
                    JSONObject json = JSONUtil.parseObj(JSONUtil.toJsonStr(map));
                    PredicateDefinition predicateDefinition = new PredicateDefinition();
                    //路由条件添加异常问题,原因是部分路由条件参数需要设置固定key
                    String name = json.getStr("name");
                    predicateDefinition.setName(name);
                    //路由条件是否拼接Key
                    if (ArrayUtil.contains(GEN_KEY_ROUTERS, name)) {
                        JSONArray jsonArray = json.getJSONArray("args");
                        for (int j = 0; j < jsonArray.size(); j++) {
                            predicateDefinition.addArg("_genkey" + j, jsonArray.get(j).toString());
                        }
                    } else {
                        JSONObject jsonObject = json.getJSONObject("args");
                        if (ObjectUtil.isNotEmpty(jsonObject)) {
                            for (Map.Entry<String, Object> entry : jsonObject.entrySet()) {
                                Object valueObj = entry.getValue();
                                if (ObjectUtil.isNotEmpty(valueObj)) {
                                    predicateDefinition.addArg(entry.getKey(), valueObj.toString());
                                }
                            }
                        }
                    }
                    // 路由条件添加异常问题,原因是部分路由条件参数需要设置固定key
                    predicateDefinitionList.add(predicateDefinition);
                }
                route.setPredicates(predicateDefinitionList);
            }

            Object filters = obj.get("filters");
            if (filters != null) {
                JSONArray list = JSONUtil.parseArray(filters.toString());
                List<FilterDefinition> filterDefinitionList = new ArrayList<>();
                if (ObjectUtil.isNotEmpty(list)) {
                    for (Object map : list) {
                        JSONObject json = (JSONObject) map;
                        JSONArray jsonArray = json.getJSONArray("args");
                        String name = json.getStr("name");
                        FilterDefinition filterDefinition = new FilterDefinition();
                        for (Object o : jsonArray) {
                            JSONObject params = (JSONObject) o;
                            filterDefinition.addArg(params.getStr("key"), params.get("value").toString());
                        }
                        filterDefinition.setName(name);
                        filterDefinitionList.add(filterDefinition);
                    }
                    route.setFilters(filterDefinitionList);
                }
            }
            ls.add(route);
        }
        return ls;
    }

    @Override
    public void setApplicationEventPublisher(@SuppressWarnings("NullableProblems") ApplicationEventPublisher applicationEventPublisher) {
        this.publisher = applicationEventPublisher;
    }
}
